<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>nginx 子目录反向代理到 gunicorn 应用</title>

    <link rel="stylesheet" href="../theme/css/style.css">
    <link rel="stylesheet" href="../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>nginx 子目录反向代理到 gunicorn 应用</h1>    <p>
        under
            <a href="../tags/gunicorn/">Gunicorn</a>
            <a href="../tags/nginx/">Nginx</a>
    </p>
    <p>
        in <a href="../categories/uncategorized/">Uncategorized</a>
    </p>
    <p>Published: 2019-05-05</p>


    <!-- wp:paragraph -->

<p>几乎所有的 gunicorn+nginx 的配置教程都是把网站的根目录反向代理到
gunicorn 应用，例如 <a href="http://docs.gunicorn.org/en/stable/deploy.html">Gunicorn
文档</a>。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>部分地方提到了这种代理，但是设置不正确，例如<a href="https://stackoverflow.com/q/25475021/9634290">这篇提问</a>。</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="nt">upstream</span> <span class="nt">app_name</span> <span class="p">{</span>
    <span class="err">server</span> <span class="n">unix</span><span class="p">:</span><span class="o">/</span><span class="n">path</span><span class="o">/</span><span class="kc">to</span><span class="o">/</span><span class="n">socket</span><span class="o">/</span><span class="n">file</span><span class="o">.</span><span class="n">sock</span> <span class="n">fail_timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">server</span> <span class="p">{</span>

   <span class="err">listen</span> <span class="err">80</span> <span class="err">default_server</span><span class="p">;</span>
   <span class="n">listen</span><span class="cp">[</span><span class="p">::</span><span class="cp">]</span><span class="p">:</span><span class="mi">80</span> <span class="n">default_server</span> <span class="n">ipv6only</span><span class="o">=</span><span class="kc">on</span><span class="p">;</span>
   <span class="err">root</span> <span class="err">/webapps/</span><span class="p">;</span>
   <span class="err">server_name</span> <span class="err">my_hostname.com</span><span class="p">;</span>

   <span class="err">location</span> <span class="err">/</span> <span class="err">{</span>
      <span class="err">proxy_set_header</span> <span class="err">Host</span> <span class="err">$http_host</span><span class="p">;</span>
   <span class="p">}</span>

   <span class="nt">location</span> <span class="o">/</span><span class="nt">appname</span><span class="o">/</span> <span class="p">{</span>
      <span class="err">proxy_set_header</span> <span class="err">X-Forwarded-For</span> <span class="err">$proxy_add_x_forwarded_for</span><span class="p">;</span>
      <span class="err">proxy_set_header</span> <span class="err">Host</span> <span class="err">$http_host</span><span class="p">;</span>
      <span class="err">proxy_set_header</span> <span class="err">X-Forwarded-Proto</span> <span class="err">$scheme</span><span class="p">;</span>
      <span class="err">proxy_redirect</span> <span class="err">off</span><span class="p">;</span>

      <span class="err">proxy_pass</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">app_name</span><span class="o">/</span><span class="p">;</span>
   <span class="p">}</span>
<span class="err">}</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>这种设置下，如果用户访问 /appname/some/path，gunicorn 应用只能看到
/some/path 这一部分，对于像 REST API 这类可能需要生成完整的 API
路径的应用，其生成的 API 的 /appname/ 部分就会丢失。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>这种需求是真实存在的，即 gunicorn 应用需要知道它在 web
服务器的什么路径下运行，以正确处理用户访问的路径和 WSGI
应用中使用的路径不一致的情况。不过好在 WSGI
规范<a href="https://www.python.org/dev/peps/pep-0333/#id19">考虑了这种情况</a>（参照
environ Variables 一节），提供了 SCRIPT_NAME 的解决方案。Gunicorn
文档把相关内容<a href="http://docs.gunicorn.org/en/stable/faq.html#how-do-i-set-script-name">放在了 FAQ
中</a>，用户可以通过环境变量或
HTTP 消息头字段设置 SCRIPT_NAME。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>仅仅在上述配置中加入</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">proxy_set_header SCRIPT_NAME /appname;</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>是不够的，因为 gunicorn 要根据 req.path 和 SCRIPT_NAME
综合判断用户访问的路径和应用内的路径分别是什么，否则会<a href="https://github.com/benoitc/gunicorn/issues/226">返回内部服务器错误
502</a>（错误代码应该没有错，因为问题发生在
nginx 和 gunicorn 之间，只是 gunicorn
没有明确地判断和处理这种情况，锅可能要算在 gunicorn 头上）。即 req.path
开始部分应该和 SCRIPT_NAME
一致，<a href="http://albertoconnor.ca/hosting-django-under-different-locations.html">这篇文章</a>讨论了这种应用场景并且给出了详细的设置。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>最终的反向代理设置应该为：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code>   <span class="nt">location</span> <span class="o">/</span><span class="nt">appname</span><span class="o">/</span> <span class="p">{</span>
      <span class="err">proxy_set_header</span> <span class="err">X-Forwarded-For</span> <span class="err">$proxy_add_x_forwarded_for</span><span class="p">;</span>
      <span class="err">proxy_set_header</span> <span class="err">Host</span> <span class="err">$http_host</span><span class="p">;</span>
      <span class="err">proxy_set_header</span> <span class="err">X-Forwarded-Proto</span> <span class="err">$scheme</span><span class="p">;</span>
      <span class="err">proxy_set_header</span> <span class="err">SCRIPT_NAME</span> <span class="err">/appname</span><span class="p">;</span>
      <span class="err">proxy_redirect</span> <span class="err">off</span><span class="p">;</span>

      <span class="err">proxy_pass</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">app_name</span><span class="o">/</span><span class="n">appname</span><span class="o">/</span><span class="p">;</span>
   <span class="p">}</span>
</code></pre></div>


<!-- /wp:code -->
    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

        <a href="../categories/">categories</a>
        <a href="..">&larr; Home</a>
</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>